package mysql;

import com.goldhuman.Common.Marshal.OctetsStream;
import com.goldhuman.Common.Octets;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import xdb.Trace;
import xdb.Xdb;
import xdb.util.Misc;

import java.sql.*;
import java.util.Map;

public class MySqlMgr4RollBack {
    public static final String V8 = "8.0";
    private static final String DRIVER_CLASS_V5 = "com.mysql.jdbc.Driver";
    private static final String DRIVER_CLASS_V8 = "com.mysql.cj.jdbc.Driver";
    public static int MS_DEBUG = 30;//毫秒
    public static int MS_INFO = 200;//毫秒
    //如果失败，就从缓存清理，以后万一出现考虑不到存储的错误，用来清理错误缓存数据，避免影响正常数据存储
    public static Map<String, Boolean> failedClearMap = Misc.newConcurrentMap();
    private static MySqlMgr4RollBack mysqlMgr = null;
    private HikariDataSource readConn = null;
    private MySQLConf mySQLConf = null;
    private final Map<String, Integer> queryNullMap = Misc.newConcurrentMap();
    private final Map<String, Integer> queryDataMap = Misc.newConcurrentMap();

    private MySqlMgr4RollBack() {
        try {
            this.mySQLConf = new MySQLConf();
            if (!Xdb.isDBServer()) {
                return;
            }

            if (!this.mySQLConf.Init("db_rollback.conf")) {
                Trace.error("MySQL Conf Init Error, Please Check File db.conf");
                return;
            }

            MS_DEBUG = this.mySQLConf.getMsDebug();
            MS_INFO = this.mySQLConf.getMsInfo();
            this.readConn = new HikariDataSource(this.InitHikariConfig(true, this.mySQLConf.getMinPoolSize(), this.mySQLConf.getMaxPoolSize()));
            if (this.readConn == null) {
                Trace.error("MySQL Connection Pool Init Error, System Exit!");
                return;
            }

            Trace.info("MySQL Connection Pool Init OK!");
        } catch (Exception e) {
            Trace.error("MySQL Connection Init Error!", e);
        }

    }

    public static MySqlMgr4RollBack GetInstance() {
        if (mysqlMgr == null) {
            mysqlMgr = new MySqlMgr4RollBack();
        }

        return mysqlMgr;
    }

    private HikariConfig InitHikariConfig(boolean autoCommit, int minPoolSize, int maxPoolSize) {
        HikariConfig hikariConfig = new HikariConfig();
        if (V8.equals(this.mySQLConf.getVersion())) {
            hikariConfig.setDriverClassName(DRIVER_CLASS_V8);
            hikariConfig.setJdbcUrl("jdbc:mysql://" + this.mySQLConf.getIp() + ":" + this.mySQLConf.getPassword() + "/" + this.mySQLConf.getDbName() + "?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC&rewriteBatchedStatements=true&connectTimeout=30000&socketTimeout=60000");
        } else {
            hikariConfig.setDriverClassName(DRIVER_CLASS_V5);
            hikariConfig.setJdbcUrl("jdbc:mysql://" + this.mySQLConf.getIp() + ":" + this.mySQLConf.getPort() + "/" + this.mySQLConf.getDbName() + "?useUnicode=true&characterEncoding=utf8&useSSL=false&rewriteBatchedStatements=true&connectTimeout=30000&socketTimeout=60000");
        }

        hikariConfig.setUsername(this.mySQLConf.getUser());
        hikariConfig.setPassword(this.mySQLConf.getPassword());
        hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
        hikariConfig.addDataSourceProperty("prepStmtCacheSize", 512);
        hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
        hikariConfig.setConnectionTimeout(1000L);//1秒
        hikariConfig.setConnectionTestQuery("SELECT 1");
        hikariConfig.setAutoCommit(autoCommit);
        hikariConfig.setMinimumIdle(minPoolSize);
        hikariConfig.setMaximumPoolSize(maxPoolSize);
        return hikariConfig;
    }

    public void Exit() {
        try {
            if (this.readConn != null) {
                this.readConn.close();
                Trace.info("Read Conn Close OK!");
            }

            mysqlMgr = null;
        } catch (Exception e) {
            Trace.error("conn close Error!", e);
        }

    }

    public Connection GetReadConn() throws SQLException {
        long startTime = System.currentTimeMillis();
        try {
            return this.readConn.getConnection();
        } catch (Exception e) {
            Trace.error("read conn get Error!", e);
            //readConn.resumePool();
            throw new SQLException();
        } finally {
            long cost = System.currentTimeMillis() - startTime;
            if (cost > MS_DEBUG) {
                xdb.Trace.debug("read conn get cost too long! use" + cost);
            }
            if (cost > MS_INFO) {
                xdb.Trace.info("read conn get cost too long! use" + cost);
            }
        }
    }

    public OctetsStream ExecQuery(String sql) throws SQLException {
        Connection conn = null;
        PreparedStatement pst = null;

        try {
            long tm = System.currentTimeMillis();
            OctetsStream octStream = null;
            conn = this.GetReadConn();
            pst = conn.prepareStatement(sql);
            ResultSet ret = pst.executeQuery();
            if (ret.next()) {
                if (Xdb.getInstance().isJson()) {
                    String value = ret.getString(1);
                    octStream = (new OctetsStream()).marshalJson(value);
                } else {
                    Blob v = ret.getBlob(1);
                    octStream = OctetsStream.wrap(Octets.wrap(v.getBytes(1L, (int) v.length())));
                }

                if (Trace.isDebugEnabled()) {
                    if (!this.queryDataMap.containsKey(sql)) {
                        this.queryDataMap.put(sql, 1);
                    } else {
                        this.queryDataMap.put(sql, this.queryDataMap.get(sql) + 1);
                    }

                    Trace.debug("Find Times " + this.queryDataMap.get(sql) + ", SQL is " + sql);
                }
            } else if (Trace.isDebugEnabled()) {
                if (!this.queryNullMap.containsKey(sql)) {
                    this.queryNullMap.put(sql, 1);
                } else {
                    this.queryNullMap.put(sql, this.queryNullMap.get(sql) + 1);
                }

                Trace.debug("Not Find Times " + this.queryNullMap.get(sql) + ", SQL is " + sql);
            }

            long useTime = System.currentTimeMillis() - tm;
            if (useTime > MS_DEBUG) {
                Trace.debug("Execute Query Use " + useTime + ", sql[" + sql + "]");
            }
            if (useTime > MS_INFO) {
                Trace.info("Execute Query Use " + useTime + ", sql[" + sql + "]");
            }

            return octStream;
        } catch (Exception e) {
            Trace.error("SQL is " + sql, e);
            throw new SQLException();
        } finally {
            if (pst != null) {
                try {
                    pst.close();
                } catch (SQLException e) {
                    Trace.error("read pst close", e);
                }
            }

            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    Trace.error("read conn close", e);
                }
            }
        }
    }
}
